/* Polynom.cpp  file
   -----------------
   Implementation of the Polynom class methods

 */

#include <iostream>
#include <assert.h>     // assert error handler
#include "Polynom.h"

using namespace std;

// arithmetic addition: Monom + Polynom
const Polynom operator+(const Monom &m, const Polynom &p)
{
	// Create polynom object out of m
    Polynom temp(m);

	// Create polynom object out of p
	Polynom p2(p);

	return (temp.DoOp(p2,'+'));
}


// arithmetic subtraction: Monom - Polynom
const Polynom operator-(const Monom &m, const Polynom &p)
{
	// Create polynom object out of m
    Polynom temp(m);

	// Create polynom object out of p
	Polynom p2(p);

	return (temp.DoOp(p2,'-'));
}


// operator for cout << Polynom
ostream& operator <<(ostream &out, const Polynom &p)
{
    mNode *iter = p.head;

	int i = 0;

    // Print linked-list of Monom's
    while (iter != NULL)
    {
		if ((i > 0) && (iter->m.getC() > 0))
			cout << "+";
        out << iter->m << " ";
        iter = iter->next;
		i++;
    }

    return out;
}

// Default empty constructor
Polynom::Polynom()
{
//    this->head = NULL;
    this->head = new mNode;

    // Memory allocation failed ?
    assert(this->head != NULL);

	// set Polynom to one zero 0 Monom
	this->head->next = NULL;
}

// Copy Constructor (Polynom)
Polynom::Polynom(const Polynom &p)
{

	// Allocate memory
    this->head = new mNode;

    // Memory allocation failed ?
    assert(this->head != NULL);

    // Set pointers to mNode
    mNode *iterSrc = p.head;
    mNode *iterDst = this->head;

    while (iterSrc->next != NULL)
    {
        iterDst->m = iterSrc->m;
        iterDst->next = new mNode;
        assert(iterDst->next != NULL);  // mem alloc failed ?
        iterDst = iterDst->next;
        iterSrc = iterSrc->next;
    }
    // Copy last node
    iterDst->m = iterSrc->m;
    iterDst->next = NULL;
	
}


// Copy Constructor (Monom)
Polynom::Polynom(const Monom &m)
{
	// Allocate memory for linked list
    this->head = new mNode;

    // Memory allocation failed ?
    assert(this->head != NULL);

    // Invoke Monom Copy Constrcutor
    this->head->m = m;

    this->head->next = NULL;
}

// Destructor
Polynom::~Polynom()
{
	// Release all allocated memory
	mNode *iterPrev = this->head;
	mNode *iterNext = iterPrev->next;
	while (iterPrev != NULL)
	{
		delete iterPrev;
		iterPrev = iterNext;
		if (iterNext != NULL)
			iterNext = iterNext->next;
	}
}

// assignment operator '='
Polynom& Polynom::operator=(const Polynom &p)
{
	// Self assignment ? protection against: p1 = p1.derive();
	if (this == &p)
		return *this;

	// Delete current polynom linked list before assignment of values
	mNode *iterPrev = this->head;
	mNode *iterNext = iterPrev->next;
	while (iterPrev != NULL)
	{
		delete iterPrev;
		iterPrev = iterNext;
		if (iterNext != NULL)
			iterNext = iterNext->next;
	}

	// Copy all Monom Nodes (linked list)
    this->head = new mNode;

    // Memory allocation failed ?
    assert(this->head != NULL);

    // Set pointers to mNode
    mNode *iterDst = this->head;
    mNode *iterSrc = p.head;

	// Copy all Monom nodes
    while (iterSrc->next != NULL)
    {
        iterDst->m = iterSrc->m;
        iterDst->next = new mNode;
        assert(iterDst->next != NULL);  // mem alloc failed ?
        iterDst = iterDst->next;
        iterSrc = iterSrc->next;
    }

    // Copy last Monom node
    iterDst->m = iterSrc->m;
    iterDst->next = NULL;

	return *this;
}

// arithmetic addition: Polynom + Polynom
const Polynom Polynom::operator+(const Polynom &p) const
{
 	// Create polynom object out of m
    Polynom temp(*this);

	// Create polynom object out of p
	Polynom p2(p);

	return (temp.DoOp(p2,'+'));
}

// arithmetic addition: Polynom + Monom
const Polynom Polynom::operator+(const Monom &m) const
{
	// Create polynom object out of m
    Polynom temp(*this);

	// Create polynom object out of p
	Polynom p2(m);

	return (temp.DoOp(p2,'+'));
}


// arithmetic subtraction: Polynom - Polynom
const Polynom Polynom::operator-(const Polynom &p) const
{
	// Create polynom object out of m
    Polynom temp(*this);

	// Create polynom object out of p
	Polynom p2(p);

	return (temp.DoOp(p2,'-'));
}

// arithmetic subtraction: Polynom - Monom
const Polynom Polynom::operator-(const Monom &m) const
{
	// Create polynom object out of m
    Polynom temp(*this);

	// Create polynom object out of p
	Polynom p2(m);

	return (temp.DoOp(p2,'-'));
}

// get highest exponent of Polynom
const int Polynom::getOrder() const
{
    mNode *iter = this->head;
    while (iter->next != NULL)
        iter = iter->next;

    return iter->m.getExponent();
}

// derives current object and returns *this (reference)
const Polynom& Polynom::derive()
{
	mNode *iterCur = this->head;
	while (iterCur != NULL)
	{
		iterCur->m = iterCur->m.derive();
		iterCur = iterCur->next;
	}

	// Redundant zero's ?
	iterCur = this->head;
	mNode *iterNext = iterCur->next;
	while (iterNext != NULL)
	{
		if (iterNext->m.getC() == 0)
		{
            iterCur->next = iterNext->next;
            delete iterNext;
            iterNext = iterCur->next;
            continue;
		}
		iterCur = iterNext;
		if (iterNext != NULL)
			iterNext = iterNext->next;
	}

    // Zero in the beggining ?
    if ((this->head->m.getC() == 0) && (this->head->next != NULL))
    {
        iterCur = this->head->next;
        delete this->head;
        this->head = iterCur;
    }

	return *this;
}


// performs m.C *= -1 for all Monoms in Polynom
void Polynom::allMinus()
{
	mNode *iter = this->head;
	while (iter != NULL)
	{
		iter->m.setC(iter->m.getC() * -1);
		iter = iter->next;
	}
}


// arithmetic '+' and '-' method
Polynom& Polynom::DoOp(Polynom &p2, char OpType)
{
	// p1 == this
	// p2 == p2 as argument

	// Is one of the objects is 0 ?	
	if (OpType == '+') {
		if (p2.head->m.getC() == 0)
			return *this;

		else if (this->head->m.getC() == 0)
			return p2;
	}
	else if (OpType == '-')
	{
		if (p2.head->m.getC() == 0)
			return *this;

        // minus operation is like addition operation
        // so let's make life much easier
        p2.allMinus();
        if (this->head->m.getC() == 0)
			return p2;
	}

    mNode *iterP2 = p2.head;

	while (iterP2 != NULL)
    {
        // Insert one node at a time
        this->InsertSortedNode(iterP2);
        iterP2 = iterP2->next;
    }

    return *this;
}

// Addition: Polynom + mNode
void Polynom::InsertSortedNode(const mNode *pn)
{

    mNode *iterPrev = this->head;
    mNode *iterCur = iterPrev->next;
    mNode *temp;                          // create space for *pn

    // Head node exponent is equal to pn's exponent ?
    if (iterPrev->m.getExponent() == pn->m.getExponent())
    {
        // Add Coefficients
        iterPrev->m.setC(iterPrev->m.getC() + pn->m.getC());

        // Head node is zero ?
        if (iterPrev->m.getC() == 0)
        {
            // Redundant 0 in head - omit 0 and set new head to iterCur
            if (iterCur != NULL)
            {
                this->head = iterCur;
                delete iterPrev;
            }
        }
        return;
    }

    // Head node exponent is larger than *pn ?
    if (pn->m.getExponent() < iterPrev->m.getExponent())
    {
        // Insert *pn as this->head
        temp = new mNode;
        assert(temp != NULL);  // mem alloc failed ?
        temp->m = pn->m;
        temp->next = this->head;
        this->head = temp;
        return;
    }

    // Node in the middle ?
    while (iterCur != NULL)
    {
        // Insert pn in-between p2
        if ((pn->m.getExponent() > iterPrev->m.getExponent()) && (pn->m.getExponent() < iterCur->m.getExponent()))
        {
            temp = new mNode;
            assert(temp != NULL);  // mem alloc failed ?
            temp->m = pn->m;
            temp->next = iterCur;
            iterPrev->next = temp;
            return;
        }
        
         // Add pn with iterCur
        else if (iterCur->m.getExponent() == pn->m.getExponent())
        {
            iterCur->m.setC(iterCur->m.getC() + pn->m.getC());

            // Result of addition equals zero ?
            if (iterCur->m.getC() == 0)
            {
                if (iterCur->next == NULL)
                    // Remove redundant zero result in the end
                    iterPrev->next = NULL;
                else
                    // Remove redundant zero result in the middle
                    iterPrev->next = iterCur->next;

                delete iterCur;
            }
            return;
        }

        iterPrev = iterCur;
        if (iterCur != NULL)
            iterCur = iterCur->next;
    }

    // Insert node in the end
    temp = new mNode;
    assert(temp != NULL);  // mem alloc failed ?
    temp->m = pn->m;
    iterPrev->next = temp;
    temp->next = NULL;

}
